/**
 * 
 */
package com.solt.prism.core.wrapper;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

import org.lobobrowser.html.AbstractHtmlRendererContext;
import org.lobobrowser.html.FormInput;
import org.lobobrowser.html.UserAgentContext;
import org.lobobrowser.html.domimpl.HTMLDocumentImpl;
import org.lobobrowser.html.parser.DocumentBuilderImpl;
import org.lobobrowser.html.parser.InputSourceImpl;
import org.lobobrowser.util.Strings;

public class LocalHtmlRendererContext extends AbstractHtmlRendererContext {
	// We need a renderer context to do form submission,
	// but we don't need to extend SimpleHtmlRendererContext
	// which is a GUI-based context. This simple
	// implementation should be enough.
	private final UserAgentContext uacontext;
	private HTMLDocumentImpl document;
	private static final Logger logger = Logger
			.getLogger(LocalHtmlRendererContext.class.getName());
	static {
		LogManager.getLogManager().reset();
		Logger globalLogger = Logger.getLogger(java.util.logging.Logger.GLOBAL_LOGGER_NAME);
		globalLogger.setLevel(java.util.logging.Level.OFF);
	}
	public LocalHtmlRendererContext(final UserAgentContext uacontext) {
		this.uacontext = uacontext;
	}

	public HTMLDocumentImpl getCurrentDocument() {
		// This field is set by the local submitForm()
		// implementation.
		return this.document;
	}

	@Override
	public boolean isImageLoadingEnabled() {
		// We don't need to load images.
		return false;
	}

	@Override
	public UserAgentContext getUserAgentContext() {
		// For consistency, this should return the
		// same UserAgentContext that was used to
		// parse the document.
		return this.uacontext;
	}

	public void navigate(String urlOrPath) {
		try {
			// We implement a convenience navigate() method
			// that is based on submitForm().
			URL url = org.lobobrowser.util.Urls.guessURL(urlOrPath);
			this.submitForm("GET", url, "_this", null, null);
		} catch (java.net.MalformedURLException mfu) {
			logger.log(Level.WARNING, "navigate()", mfu);
		}
	}

	public void navigate(URL url) {
		this.submitForm("GET", url, "_this", null, null);
	}

	@Override
	public void submitForm(String method, URL action, String target,
			String enctype, FormInput[] formInputs) {
		// This is the code that does form submission.
		try {
			final String actualMethod = method.toUpperCase();
			URL resolvedURL;
			if ("GET".equals(actualMethod) && formInputs != null) {
				boolean firstParam = true;
				StringBuffer newUrlBuffer = new StringBuffer(
						action.toExternalForm());
				if (action.getQuery() == null) {
					newUrlBuffer.append("?");
				} else {
					newUrlBuffer.append("&");
				}
				for (int i = 0; i < formInputs.length; i++) {
					FormInput parameter = formInputs[i];
					String name = parameter.getName();
					String encName = URLEncoder.encode(name, "UTF-8");
					if (parameter.isText()) {
						if (firstParam) {
							firstParam = false;
						} else {
							newUrlBuffer.append("&");
						}
						String valueStr = parameter.getTextValue();
						String encValue = URLEncoder.encode(valueStr, "UTF-8");
						newUrlBuffer.append(encName);
						newUrlBuffer.append("=");
						newUrlBuffer.append(encValue);
					}
				}
				resolvedURL = new java.net.URL(newUrlBuffer.toString());
			} else {
				resolvedURL = action;
			}
			URL urlForLoading;
			if (resolvedURL.getProtocol().equals("file")) {
				// Remove query so it works.
				try {
					String ref = action.getRef();
					String refText = ref == null || ref.length() == 0 ? ""
							: "#" + ref;
					urlForLoading = new URL(resolvedURL.getProtocol(),
							action.getHost(), action.getPort(),
							action.getPath() + refText);
				} catch (java.net.MalformedURLException throwable) {
					urlForLoading = action;
				}
			} else {
				urlForLoading = resolvedURL;
			}
			// Using potentially different URL for loading.
			boolean isPost = "POST".equals(actualMethod);
			URLConnection connection = urlForLoading.openConnection();
			connection.setRequestProperty("User-Agent", getUserAgentContext()
					.getUserAgent());
			connection.setRequestProperty("Cookie", "");
			HttpURLConnection hc = null;
			if (connection instanceof HttpURLConnection) {
				hc = (HttpURLConnection) connection;
				hc.setRequestMethod(actualMethod);
				// Do not follow redirects
				hc.setInstanceFollowRedirects(false);
			}
			if (isPost) {
				connection.setDoOutput(true);
				ByteArrayOutputStream bufOut = new ByteArrayOutputStream();
				boolean firstParam = true;
				if (formInputs != null) {
					for (int i = 0; i < formInputs.length; i++) {
						FormInput parameter = formInputs[i];
						String name = parameter.getName();
						String encName = URLEncoder.encode(name, "UTF-8");
						if (parameter.isText()) {
							if (firstParam) {
								firstParam = false;
							} else {
								bufOut.write((byte) '&');
							}
							String valueStr = parameter.getTextValue();
							String encValue = URLEncoder.encode(valueStr,
									"UTF-8");
							bufOut.write(encName.getBytes("UTF-8"));
							bufOut.write((byte) '=');
							bufOut.write(encValue.getBytes("UTF-8"));
						}
					}
				}
				byte[] postContent = bufOut.toByteArray();
				if (connection instanceof HttpURLConnection) {
					((HttpURLConnection) connection)
							.setFixedLengthStreamingMode(postContent.length);
				}
				connection.setRequestProperty("Content-Type",
						"application/x-www-form-urlencoded");
				OutputStream postOut = connection.getOutputStream();
				postOut.write(postContent);
				postOut.flush();
			}
			if (hc != null) {
				// We handle redirects.
				int responseCode = hc.getResponseCode();
				if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
						|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
					String location = hc.getHeaderField("Location");
					URL newAction = new URL(action, location);
					// Recurse
					this.submitForm("GET", newAction, target, null, null);
					return;
				}
			}
			InputStream in = connection.getInputStream();
			
			try {
				InputStream bin = new BufferedInputStream(in, 8192);
				String contentEncoding = connection.getContentEncoding();
				if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
					bin = new GZIPInputStream(bin);
				}
				String actualURI = urlForLoading.toExternalForm();
				// Note that DocumentBuilderImpl needs to be
				// constructed by passing both a UserAgentContext
				// and an HtmlRendererContext in this case, so
				// that form.submit() can take effect.
				DocumentBuilderImpl builder = new DocumentBuilderImpl(
						this.uacontext, this);
				String charset = getCharset(connection);
				InputSourceImpl is = new InputSourceImpl(bin, actualURI,
						charset);
				this.document = (HTMLDocumentImpl) builder.parse(is);
			} finally {
				in.close();
			}
		} catch (Exception err) {
			this.document = null;
			System.err.println("submitForm() url=" + action);
		}
	}

	public static String getCharset(URLConnection connection) {
		String contentType = connection.getContentType();
		if (contentType == null) {
			return getDefaultCharset(connection);
		}
		StringTokenizer tok = new StringTokenizer(contentType, ";");
		if (tok.hasMoreTokens()) {
			tok.nextToken();
			while (tok.hasMoreTokens()) {
				String assignment = tok.nextToken().trim();
				int eqIdx = assignment.indexOf(61);
				if (eqIdx != -1) {
					String varName = assignment.substring(0, eqIdx).trim();
					if ("charset".equalsIgnoreCase(varName)) {
						String varValue = assignment.substring(eqIdx + 1);
						return Strings.unquote(varValue.trim());
					}
				}
			}
		}
		return getDefaultCharset(connection);
	}

	private static String getDefaultCharset(URLConnection connection) {
		URL url = connection.getURL();
		if (org.lobobrowser.util.Urls.isLocalFile(url)) {
			String charset = System.getProperty("file.encoding");
			return ((charset == null) ? "UTF-8" : charset);
		}

		return "UTF-8";
	}
}